package com.wkh.maker.template;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.wkh.maker.meta.Meta;
import com.wkh.maker.meta.enums.FileGeneratorEnum;
import com.wkh.maker.meta.enums.FileTypeEnum;
import com.wkh.maker.template.model.TemplateMakerConfig;
import com.wkh.maker.template.model.TemplateMakerFileConfig;
import com.wkh.maker.template.model.TemplateMakerModelConfig;
import com.wkh.maker.template.model.TemplateMakerOutputConfig;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wangkehua
 * 模板制作工具
 */
public class TemplateMaker {

    /**
     * 制作模板
     * @param templateMakerConfig
     * @return
     */
    public static long makeTemplate(TemplateMakerConfig templateMakerConfig){
        Long id = templateMakerConfig.getId();
        String originProjectPath = templateMakerConfig.getOriginProjectPath();
        Meta meta = templateMakerConfig.getMeta();
        TemplateMakerFileConfig templateMakerFileConfig = templateMakerConfig.getFileConfig();
        TemplateMakerModelConfig templateMakerModelConfig = templateMakerConfig.getModelConfig();
        TemplateMakerOutputConfig templateMakerOutputConfig = templateMakerConfig.getTemplateMakerOutputConfig();
        return makeTemplate(meta, originProjectPath, templateMakerFileConfig, templateMakerModelConfig, id,templateMakerOutputConfig);
    }

    /**
     * 制作模板
     *
     * @param newMeta                 元信息文件
     * @param originProjectPath       原始路径
     * @param templateMakerFileConfig 在这里面封装了 文件配置信息 也就是文件配置信息
     * @param templateMakerModelConfig  再这个对象中封装了模型参数和需要挖坑的字符串
     * @param id
     * @param templateMakerOutputConfig
     * @return
     */
    public static long makeTemplate(Meta newMeta, String originProjectPath, TemplateMakerFileConfig templateMakerFileConfig, TemplateMakerModelConfig templateMakerModelConfig, Long id, TemplateMakerOutputConfig templateMakerOutputConfig) {
        //id不存在
        if (id == null) {
            //生成id
            id = IdUtil.getSnowflakeNextId();
        }
        //复制目录
        String projectPath = System.getProperty("user.dir");
        String tempDirPath = projectPath + File.separator + ".temp";
        String templatePath = tempDirPath + File.separator + id;
        if (!FileUtil.exist(templatePath)) {
            FileUtil.mkdir(tempDirPath);
            FileUtil.copy(originProjectPath, templatePath, true);
        }
        //一、输入信息
        //处理模型信息
        ArrayList<Meta.ModelConfig.ModelInfo> newModelInfoList = getModelInfoList(templateMakerModelConfig);
        //输入文件信息
        //要挖坑的项目的根目录
        String sourceRootPath = FileUtil.loopFiles(new File(templatePath),1,null).stream()
                .filter(File::isDirectory)
                .findFirst()//取第一个
                .orElseThrow(RuntimeException::new)
                .getAbsolutePath();
        sourceRootPath = sourceRootPath.replace("\\", "/");

        //二、生成文件配置信息
        ArrayList<Meta.FileConfig.FileInfo> newFileInfoList = makeFileTemplates(templateMakerFileConfig, templateMakerModelConfig, sourceRootPath);
        //三、生成配置文件
        String metaOutputPath = templatePath + File.separator + "meta.json";
        //0.判断meta.json配置文件是否存在
        if (FileUtil.exist(metaOutputPath)) {
            //已有，则在已有的meta.json文件上进行修改
            //先读取meta.json文件
            Meta oldMeta = JSONUtil.toBean(FileUtil.readUtf8String(metaOutputPath), Meta.class);
            //进行追加
            List<Meta.FileConfig.FileInfo> oldFinleInfoList = oldMeta.getFileConfig().getFiles();
            List<Meta.ModelConfig.ModelInfo> oldModelList = oldMeta.getModelConfig().getModels();
            oldFinleInfoList.addAll(newFileInfoList);
            oldModelList.addAll(newModelInfoList);
            //配置去重
            oldMeta.getFileConfig().setFiles(distinctFiles(oldFinleInfoList));
            oldMeta.getModelConfig().setModels(distinctModels(oldModelList));
            BeanUtil.copyProperties(oldMeta,newMeta);
        } else {
            Meta.FileConfig fileConfig = new Meta.FileConfig();
            fileConfig.setSourceRootPath(sourceRootPath);
            fileConfig.setOutputRootPath("generated");
            fileConfig.setType(FileTypeEnum.DIR.getValue());
            ArrayList<Meta.FileConfig.FileInfo> fileInfoList = new ArrayList<>();
            fileInfoList.addAll(newFileInfoList);
            fileConfig.setFiles(fileInfoList);
            newMeta.setFileConfig(fileConfig);

            Meta.ModelConfig modelConfig = new Meta.ModelConfig();
            ArrayList<Meta.ModelConfig.ModelInfo> modelInfoList = new ArrayList<>();
            modelInfoList.addAll(newModelInfoList);
            modelConfig.setModels(modelInfoList);
            newMeta.setModelConfig(modelConfig);
        }
        //额外的输出配置
        if (templateMakerOutputConfig != null){
            if (templateMakerOutputConfig.isRemoveGroupFilesFromRoot()){
                //过滤掉外层文件中的内层文件中出现的文件
                List<Meta.FileConfig.FileInfo> files = newMeta.getFileConfig().getFiles();
                newMeta.getFileConfig().setFiles(TemplateMakerUtils.removeGroupFilesFromRoot(files));
            }
        }
        //输出元信息对象
        FileUtil.writeUtf8String(JSONUtil.toJsonPrettyStr(newMeta), metaOutputPath);
        return id;
    }

    /**
     * 获取模型信息列表
     *
     * @param templateMakerModelConfig
     * @return
     */
    private static ArrayList<Meta.ModelConfig.ModelInfo> getModelInfoList(TemplateMakerModelConfig templateMakerModelConfig) {
        //本次新增的模型列表
        ArrayList<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>();
        //非空校验
        if (templateMakerModelConfig == null) {
            return newModelInfoList;
        }
        List<TemplateMakerModelConfig.ModelInfoConfig> models = templateMakerModelConfig.getModels();
        if (CollUtil.isEmpty(models)){
            return newModelInfoList;
        }
        //转换为配置文件接收的 modelInfo 对象
        List<Meta.ModelConfig.ModelInfo> inputModelInfoList = models.stream()
                .map(modelInfoConfig -> {
                    Meta.ModelConfig.ModelInfo modelInfo = new Meta.ModelConfig.ModelInfo();
                    BeanUtil.copyProperties(modelInfoConfig, modelInfo);
                    return modelInfo;
                })
                .collect(Collectors.toList());

        //如果是模型组
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        if (modelGroupConfig != null) {
            Meta.ModelConfig.ModelInfo groupModelInfo = new Meta.ModelConfig.ModelInfo();
            BeanUtil.copyProperties(modelGroupConfig, groupModelInfo);
            //模型全放到一个分组内
            groupModelInfo.setModels(inputModelInfoList);
            newModelInfoList = new ArrayList<>();
            newModelInfoList.add(groupModelInfo);
        } else {
            //不分组，添加所有的模型信息列表
            newModelInfoList.addAll(inputModelInfoList);
        }
        return newModelInfoList;
    }

    /**
     * 生成多个文件配置信息
     * @param templateMakerFileConfig
     * @param templateMakerModelConfig
     * @param sourceRootPath
     * @return
     */
    private static ArrayList<Meta.FileConfig.FileInfo> makeFileTemplates(TemplateMakerFileConfig templateMakerFileConfig, TemplateMakerModelConfig templateMakerModelConfig, String sourceRootPath) {
        //遍历输入文件
        ArrayList<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>();
        //非空校验
        if (templateMakerFileConfig == null){
            return newFileInfoList;
        }
        //获取文件配置集合
        List<TemplateMakerFileConfig.FileInfoConfig> fileInfoConfigs = templateMakerFileConfig.getFiles();
        if (CollUtil.isEmpty(fileInfoConfigs)){
            return newFileInfoList;
        }
        for (TemplateMakerFileConfig.FileInfoConfig fileInfoConfig : fileInfoConfigs) {
            String fileInputPath = fileInfoConfig.getPath();
            String inputFileAbsolutePath = sourceRootPath + "/" + fileInputPath;
            //传入的是绝对路径并且得到过滤后的文件列表
            List<File> fileList = FileFilter.doFilter(fileInfoConfig.getFilterConfigList(), inputFileAbsolutePath);
            //不处理已经生成的ftl模板文件
            fileList = fileList.stream()
                    .filter(file -> !file.getAbsolutePath().endsWith(".ftl"))
                    .collect(Collectors.toList());
            for (File file : fileList) {
                Meta.FileConfig.FileInfo fileInfo = makeFileTemplate(templateMakerModelConfig, sourceRootPath, file, fileInfoConfig);
                newFileInfoList.add(fileInfo);
            }
        }

        //如果是文件组
        TemplateMakerFileConfig.FileGroupConfig fileGroupConfig = templateMakerFileConfig.getFileGroupConfig();
        if (fileGroupConfig != null){
            String condition = fileGroupConfig.getCondition();
            String groupName = fileGroupConfig.getGroupName();
            String groupKey = fileGroupConfig.getGroupKey();
            //创建一个新的分组
            Meta.FileConfig.FileInfo groupFileInfo = new Meta.FileConfig.FileInfo();
            groupFileInfo.setCondition(condition);
            groupFileInfo.setGroupName(groupName);
            groupFileInfo.setGroupKey(groupKey);
            groupFileInfo.setType(FileTypeEnum.GROUP.getValue());
            //文件全放到一个分组内
            groupFileInfo.setFiles(newFileInfoList);
            newFileInfoList = new ArrayList<>();
            newFileInfoList.add(groupFileInfo);
        }
        return newFileInfoList;
    }

    /**
     * 文件去重
     *
     * @param fileInfoList
     * @return
     */
    private static List<Meta.FileConfig.FileInfo> distinctFiles(List<Meta.FileConfig.FileInfo> fileInfoList) {
        //1.将所有文件配置分为有分组的和无分组的
        //先处理有分组的文件
        //以组为单位划分
        /**
         * 例子 ： {"groupKey":"a",file:[1,2]} , {"groupKey":"a",file:[2,3]} , {"groupKey":"b",file:[4,5]}
         *        {"groupKey":"a",file:[ [1,2],[2,3]]} , {"groupKey":"b",file:[4,5]}
         */
        Map<String, List<Meta.FileConfig.FileInfo>> groupKeyFileInfoMap = fileInfoList.stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getGroupKey()))
                .collect(Collectors.groupingBy(Meta.FileConfig.FileInfo::getGroupKey));
        //2.对于有分组的文件配置，如果有相同的分组，同分组中的文件进行合并(merged)，不同分组可同时保留
        //同组内文件配置的合并
        /**
         * {"groupKey":"a",file:[ [1,2],[2,3]]}
         * {"groupKey":"a",file:[1,2,2,3]}
         * {"groupKey":"a",file:[1,2,3]}
         */
        //合并后的对象 map
        HashMap<String, Meta.FileConfig.FileInfo> groupKeyMergedFileInfoMap = new HashMap<>();
        for (Map.Entry<String,List<Meta.FileConfig.FileInfo>> entry : groupKeyFileInfoMap.entrySet()){
            List<Meta.FileConfig.FileInfo> tempFileInfoList = entry.getValue();
            //newFileInfoList = [1,2,2,3]
            List<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>(tempFileInfoList.stream()
                    .flatMap(fileInfo -> fileInfo.getFiles().stream())
                    .collect(
                            Collectors.toMap(Meta.FileConfig.FileInfo::getOutputPath,o->o,(old,replacement)->replacement)
                    )
                    .values());
            //使用新的group配置覆盖旧的
            //先获取新的group配置
            Meta.FileConfig.FileInfo newFileInfo = CollUtil.getLast(tempFileInfoList);
            //设置新的配置列表
            newFileInfo.setFiles(newFileInfoList);
            String groupKey = entry.getKey();
            groupKeyMergedFileInfoMap.put(groupKey,newFileInfo);
        }
        //3.创建新的文件配置列表(结果列表)，先将合并后的分组添加到结果列表
        //4.再将无分组的文件配置列表添加到结果列表
        ArrayList<Meta.FileConfig.FileInfo> resultList = new ArrayList<>(groupKeyMergedFileInfoMap.values());
        resultList.addAll(new ArrayList<>(fileInfoList.stream()
                .filter(fileInfo -> StrUtil.isBlank(fileInfo.getGroupKey()))
                .collect(Collectors.toMap(Meta.FileConfig.FileInfo::getOutputPath,o->o,(old,replacement)->replacement)
                ).values()));
        return resultList;
    }

    /**
     * 模型去重
     *
     * @param modelInfoList
     * @return
     */
    private static List<Meta.ModelConfig.ModelInfo> distinctModels(List<Meta.ModelConfig.ModelInfo> modelInfoList) {
        //1.将所有模型配置分为有分组的和无分组的
        //先处理有分组的模型
        //以组为单位划分
        /**
         * 例子 ： {"groupKey":"a",model:[1,2]} , {"groupKey":"a",model:[2,3]} , {"groupKey":"b",model:[4,5]}
         *        {"groupKey":"a",model:[ [1,2],[2,3]]} , {"groupKey":"b",model:[4,5]}
         */
        Map<String, List<Meta.ModelConfig.ModelInfo>> groupKeyModelInfoMap = modelInfoList.stream()
                .filter(modelInfo -> StrUtil.isNotBlank(modelInfo.getGroupKey()))
                .collect(Collectors.groupingBy(Meta.ModelConfig.ModelInfo::getGroupKey));
        //2.对于有分组的模型配置，如果有相同的分组，同分组中的模型进行合并(merged)，不同分组可同时保留
        //同组内模型配置的合并
        /**
         * {"groupKey":"a",model:[ [1,2],[2,3]]}
         * {"groupKey":"a",model:[1,2,2,3]}
         * {"groupKey":"a",model:[1,2,3]}
         */
        //合并后的对象 map
        HashMap<String, Meta.ModelConfig.ModelInfo> groupKeyMergedModelInfoMap = new HashMap<>();
        for (Map.Entry<String,List<Meta.ModelConfig.ModelInfo>> entry : groupKeyModelInfoMap.entrySet()){
            List<Meta.ModelConfig.ModelInfo> tempModelInfoList = entry.getValue();
            //newModelInfoList = [1,2,2,3]
            List<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>(tempModelInfoList.stream()
                    .flatMap(modelInfo -> modelInfo.getModels().stream())
                    .collect(
                            Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName,o->o,(old,replacement)->replacement)
                    )
                    .values());
            //使用新的group配置覆盖旧的
            //先获取新的group配置
            Meta.ModelConfig.ModelInfo newModelInfo = CollUtil.getLast(tempModelInfoList);
            //设置新的配置列表
            newModelInfo.setModels(newModelInfoList);
            String groupKey = entry.getKey();
            groupKeyMergedModelInfoMap.put(groupKey,newModelInfo);
        }
        //3.创建新的模型配置列表(结果列表)，先将合并后的分组添加到结果列表
        //4.再将无分组的模型配置列表添加到结果列表
        ArrayList<Meta.ModelConfig.ModelInfo> resultList = new ArrayList<>(groupKeyMergedModelInfoMap.values());
        resultList.addAll(new ArrayList<>(modelInfoList.stream()
                .filter(modelInfo -> StrUtil.isBlank(modelInfo.getGroupKey()))
                .collect(Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName,o->o,(old,replacement)->replacement)
                ).values()));
        return resultList;
    }

    /**
     * 制作模板文件
     *
     * @param templateMakerModelConfig
     * @param sourceRootPath
     * @param inputFile
     * @param fileInfoConfig
     * @return
     */
    private static Meta.FileConfig.FileInfo makeFileTemplate(TemplateMakerModelConfig templateMakerModelConfig, String sourceRootPath, File inputFile,
                                                             TemplateMakerFileConfig.FileInfoConfig fileInfoConfig) {
        String fileInputAbsolutePath = inputFile.getAbsolutePath();
        fileInputAbsolutePath = fileInputAbsolutePath.replace("\\", "/");
        //拿到要挖坑的文件的相对路径
        String fileInputPath = fileInputAbsolutePath.replace(sourceRootPath + "/", "");
//        fileInputPath = fileInputPath.replaceAll("\\\\", "/");
        String fileOutputPath = fileInputPath + ".ftl";

        //二、生成模板文件，并使用字符串替换
        //1.先把.java文件转换成字符串内容
        String fileOutputAbsolutePath = fileInputAbsolutePath + ".ftl";

        //获得 fileContent
        String fileContent;
        //判断是否已有模板文件
        boolean hasTemplateFile = FileUtil.exist(fileOutputAbsolutePath);
        if (hasTemplateFile) {
            //如果已有了，则在原有模板文件的基础上挖坑
            fileContent = FileUtil.readUtf8String(fileOutputAbsolutePath);
        } else {
            //不存在，把原.java文件的内容读取出来
            fileContent = FileUtil.readUtf8String(fileInputAbsolutePath);
        }
        String newFileContent = fileContent;
        //支持多个模型：对于同一个文件的内容，遍历模型进行多轮替换
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();

        String replacement;
        for (TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig : templateMakerModelConfig.getModels()) {
            String fieldName = modelInfoConfig.getFieldName();
            if (modelGroupConfig == null){
                //不是分组
                replacement = String.format("${%s}", fieldName);
            }else {
                //是分组
                String groupKey = modelGroupConfig.getGroupKey();
                replacement = String.format("${%s.%s}", groupKey,fieldName);
            }
            newFileContent = StrUtil.replace(newFileContent, modelInfoConfig.getReplaceText(), replacement);
        }

        //2.输出模板文件，输出.ftl文件

        Meta.FileConfig.FileInfo fileInfo = new Meta.FileConfig.FileInfo();
        fileInfo.setType(FileTypeEnum.FILE.getValue());
        fileInfo.setGenerateType(FileGeneratorEnum.DYNAMIC.getValue());
        fileInfo.setInputPath(fileOutputPath);
        //默认动态生成
        fileInfo.setGenerateType(FileGeneratorEnum.DYNAMIC.getValue());
        fileInfo.setOutputPath(fileInputPath);
        fileInfo.setCondition(fileInfoConfig.getCondition());
        //是否更改了文件内容
        boolean contentEquals = fileContent.equals(newFileContent);
        //之前不存在模板文件，并且这次替换没有修改文件的内容，才是静态生成
        //比较模板文件与源文件
        if (!hasTemplateFile){
            if (contentEquals) {
                //如果一致,没有挖坑，静态生成
                fileInfo.setGenerateType(FileGeneratorEnum.STATIC.getValue());
                //如果是静态生成，那么输入路径 = 输出路径
                fileInfo.setInputPath(fileInputPath);
            } else {
                //如果是动态生成，那么输出文件就是.ftl文件
                FileUtil.writeUtf8String(newFileContent, fileOutputAbsolutePath);
            }
        }else if (!contentEquals){
            //有模板文件,并且增加了新坑，生成/更新模板文件
            FileUtil.writeUtf8String(newFileContent, fileOutputAbsolutePath);
        }

        return fileInfo;
    }
}
